﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;
using Phoenix.Xna.Framework.ContentPipeline.Properties;

namespace Phoenix.Xna.Framework.ContentPipeline.Models
{
    [ContentProcessor(DisplayName = "Instanced Model - Phoenix XNA Engine")]
    public class InstancedModelProcessor : ContentProcessor<NodeContent, InstancedModelContent>
    {
        private NodeContent _rootNode;
        private ContentProcessorContext _context;
        private InstancedModelContent _outputModel;
        private Dictionary<MaterialContent, MaterialContent> _processedMaterials = new Dictionary<MaterialContent, MaterialContent>();

        private void ProcessNode(NodeContent node)
        {
            MeshHelper.TransformScene(node, node.Transform);
            node.Transform = Matrix.Identity;
            MeshContent mesh = node as MeshContent;

            if (mesh != null)
            {
                MeshHelper.OptimizeForCache(mesh);

                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    ProcessGeometry(geometry);
                }
            }

            foreach (NodeContent child in node.Children)
            {
                ProcessNode(child);
            }
        }

        private void ProcessGeometry(GeometryContent geometry)
        {
            int indexCount = geometry.Indices.Count;
            int vertexCount = geometry.Vertices.VertexCount;

            if (vertexCount > ushort.MaxValue)
            {
                throw new InvalidContentException(string.Format(Resources.GeometryOverflowException, vertexCount));
            }

            if (vertexCount > ushort.MaxValue / 8)
            {
                _context.Logger.LogWarning(null, _rootNode.Identity, Resources.GeometryWarning, vertexCount, ushort.MaxValue / vertexCount);
            }

            VertexChannelCollection vertexChannels = geometry.Vertices.Channels;

            for (int i = 1; i <= 4; i++)
            {
                if (vertexChannels.Contains(VertexChannelNames.TextureCoordinate(i)))
                {
                    throw new InvalidContentException(string.Format(Resources.TextureChannelException, i));
                }
            }

            VertexBufferContent vertexBufferContent;
            VertexElement[] vertexElements;
            geometry.Vertices.CreateVertexBuffer(out vertexBufferContent, out vertexElements, _context.TargetPlatform);
            int vertexStride = VertexDeclaration.GetVertexStrideSize(vertexElements, 0);
            MaterialContent material = ProcessMaterial(geometry.Material);
            _outputModel.AddModelPart(indexCount, vertexCount, vertexStride, vertexElements, vertexBufferContent, geometry.Indices, material);
        }

        private MaterialContent ProcessMaterial(MaterialContent material)
        {
            if (!_processedMaterials.ContainsKey(material))
            {
                EffectMaterialContent instancedMaterial = new EffectMaterialContent();
                instancedMaterial.Effect = new ExternalReference<EffectContent>("InstancedModel.fx", _rootNode.Identity);

                if (!material.Textures.ContainsKey("Texture"))
                {
                    throw new InvalidContentException(Resources.MaterialTextureNullException);
                }

                instancedMaterial.Textures.Add("Texture", material.Textures["Texture"]);
                _processedMaterials[material] = _context.Convert<MaterialContent, MaterialContent>(instancedMaterial, "MaterialProcessor");
            }

            return _processedMaterials[material];
        }

        #region ContentProcessor Members

        public override InstancedModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            _rootNode = input;
            _context = context;
            _outputModel = new InstancedModelContent();
            ProcessNode(input);
            return _outputModel;
        }

        #endregion
    }
}
